import { ArcRotateCamera, BackgroundMaterial, Color3, Color4, CubeTexture, Engine, FresnelParameters, HemisphericLight, Matrix, Mesh, MeshBuilder, MirrorTexture, Plane, ReflectionProbe, Scene, StandardMaterial, Texture, Vector3 } from "@babylonjs/core";

//反射探针(进行二次反射?)
export default class SkyBox {

    public engine: Engine;
    public scene: Scene;
    public canvas: HTMLCanvasElement;

    private updateAction: Map<number, (time: number) => {}> = new Map();
    private updateActionId: number = 0;

    public static skyBox: SkyBox;

    public constructor(canvas: HTMLCanvasElement) {
        this.canvas = canvas;
        this.engine = new Engine(canvas, true);
        this.scene = this.createScene();

        this.init();

        SkyBox.skyBox = this;
    }

    private init() {
        this.update(0);
    }

    private createScene() {
        const { engine, canvas } = this;
        let scene = new Scene(engine);
        scene.clearColor = new Color4(0.1, 0.1, 0.1, 1);

        const light = new HemisphericLight('defaultLight', new Vector3(1, 1, 0), scene);

        const camera = new ArcRotateCamera('Camera', Math.PI / 1.8, Math.PI / 3, 15, new Vector3(0, 0, 0), scene);
        camera.attachControl(canvas, true);
        camera.upperRadiusLimit = 50;
        camera.lowerRadiusLimit = 10;
        camera.upperBetaLimit = Math.PI / 6 * 5;
        camera.lowerBetaLimit = Math.PI / 6;





        const knot = MeshBuilder.CreateTorusKnot('kont', { radius: 1, tube: 0.4, radialSegments: 128, tubularSegments: 64 });
        const sphere1 = MeshBuilder.CreateSphere('sphere',);
        sphere1.setPivotMatrix(Matrix.Translation(-1, 3, 0), false);
        //等于 sphere.position.set(0,3,0);

        const sphere2 = sphere1.clone();
        sphere2.setPivotMatrix(Matrix.Translation(3, 0, 0), false);

        const sphere3 = sphere1.clone();
        sphere3.setPivotMatrix(Matrix.Translation(0, 0, 3), false);

        //镜面地面
        const mirrorTexture = new MirrorTexture('mirror', 1024, scene, true);
        mirrorTexture.mirrorPlane = new Plane(0, -1, 0, -2);
        mirrorTexture.renderList = [sphere1, sphere2, sphere3, knot];
        mirrorTexture.level = 0.7;

        const diffTexture = new Texture('https://playground.babylonjs.com/textures/amiga.jpg');
        diffTexture.uScale = 5;
        diffTexture.vScale = 5;

        const mirrorMat = new BackgroundMaterial('mittotMat');
        mirrorMat.reflectionTexture = mirrorTexture;
        mirrorMat.reflectionFresnel = true;//显示反射面颜色
        mirrorMat.reflectionStandardFresnelWeight = 0.7;
        mirrorMat.diffuseTexture = diffTexture;

        const ground = MeshBuilder.CreateGround('ground', { width: 10, height: 10 });
        ground.position.set(0, -2, 0);
        ground.material = mirrorMat;


        this._genSatelliteMat(knot, new Color3(1, 1, 1), [sphere1, sphere2, sphere3, ground], scene);
        this._genSatelliteMat(sphere1, new Color3(1, 0.5, 0.5), [knot, sphere2, sphere3, ground], scene);
        this._genSatelliteMat(sphere2, new Color3(0.5, 1, 0.5), [knot, sphere1, sphere3, ground], scene);
        this._genSatelliteMat(sphere3, new Color3(0.5, 0.5, 1), [knot, sphere1, sphere2, ground], scene);


        this.setUpdateAction((time) => {
            sphere1.rotation.y += 0.01;
            sphere2.rotation.y += 0.01;
            sphere3.rotation.y += 0.01;
            return '';
        })


        return scene;
    }

    private _genSatelliteMat(root: Mesh, color: Color3, meshes: Mesh[], scene: Scene) {
        const mat = new StandardMaterial('satelliteMat' + root.name);
        mat.diffuseColor = color;
        root.material = mat;

        const probe = new ReflectionProbe('satelliteProbe' + root.name, 512, scene);
        probe.renderList?.push(...meshes);
        probe.refreshRate = 3;//因为很消耗性能, 所以三帧渲染一次

        mat.reflectionTexture = probe.cubeTexture;
        mat.reflectionFresnelParameters = new FresnelParameters({ bias: 0.02 });//菲尼尔的衰变函数

        probe.attachToMesh(root);
    }

    private update(time: number) {
        const { updateAction, scene } = this;
        updateAction.forEach((action, key) => {
            action(time);
        });
        scene.render();
        requestAnimationFrame((time) => {
            this.update(time);
        });
    }

    public setUpdateAction(action: (time: number) => {}, id: number = -1) {
        const { updateAction } = this;
        let curId = id;
        if (id == -1) {
            curId = this.updateActionId++;
        } else if (id >= this.updateActionId) {
            curId = this.updateActionId++;
        }
        updateAction.set(curId, action);
    }
}